package cn.italkcloud.cph.helper;

import cn.italkcloud.cph.utils.StringUtil;
import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author dominate
 * @since 2024/5/20
 **/
@Slf4j
public class PhoneGeoHelper {

    private static final String PHONE_DAT_FILE_PATH = "utils/phone_location.dat";
    private static final String[] PHONE_NUMBER_TYPE = {"未知运营商", "移动", "联通", "电信", "电信虚拟运营商", "联通虚拟运营商", "移动虚拟运营商"};
    private static final int INDEX_SEGMENT_LENGTH = 9;
    private static final int DATA_FILE_LENGTH_HINT = 3747505;
    private static final String NOT_FOUND_STR = "未知";

    private static final String[] NOT_FOUND_RESULT = {};
    private static final String INFO_STR_SPLIT = "\\|";
    private static final String EXCEL_COL_SPLIT = "\t";
    private static final String EXCEL_ROW_SPLIT = "\n";

    private static byte[] dataByteArray;
    private static int indexAreaOffset;
    private static int phoneRecordCount;

    private final ByteBuffer byteBuffer;

    public PhoneGeoHelper() {
        initData();
        byteBuffer = ByteBuffer.wrap(dataByteArray);
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
    }

    private static synchronized void initData() {
        // 只加载一次
        if (dataByteArray != null) {
            return;
        }
        ByteArrayOutputStream byteData = new ByteArrayOutputStream(DATA_FILE_LENGTH_HINT);
        byte[] buffer = new byte[1024];

        int readBytesLength;
        try (InputStream inputStream = PhoneGeoHelper.class.getClassLoader().getResourceAsStream(PHONE_DAT_FILE_PATH)) {
            while (true) {
                assert inputStream != null;
                if ((readBytesLength = inputStream.read(buffer)) == -1) {
                    break;
                }
                byteData.write(buffer, 0, readBytesLength);
            }
        } catch (Exception e) {
            log.error("Can't find phone.dat in classpath: " + PHONE_DAT_FILE_PATH);
            throw new RuntimeException(e);
        }

        dataByteArray = byteData.toByteArray();
        ByteBuffer byteBuffer = ByteBuffer.wrap(dataByteArray);
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
        int dataVersion = byteBuffer.getInt();
        log.info("init data version: " + dataVersion);
        indexAreaOffset = byteBuffer.getInt();
        phoneRecordCount = (dataByteArray.length - indexAreaOffset) / INDEX_SEGMENT_LENGTH;
    }

    public String findToRow(String... phoneNumbers) {
        StringBuilder result = new StringBuilder();
        for (String phoneNumber : phoneNumbers) {
            result.append(findOneToRow(phoneNumber));
            result.append(EXCEL_ROW_SPLIT);
        }
        return result.toString();
    }

    public List<PhoneGeo> findToGeo(String... phoneNumbers) {
        List<PhoneGeo> geoList = new ArrayList<>();
        for (String phoneNumber : phoneNumbers) {
            geoList.add(findOneToGeo(phoneNumber));
        }
        return geoList;
    }

    public PhoneGeo findOneToGeo(String phoneNumber) {
        return new PhoneGeo(findOne(phoneNumber), phoneNumber);
    }

    /**
     * 0省 1市 2城市编码 3区号 4运营商
     *
     * @param phoneNumber 待查询电话号号码
     * @return 归属信息
     */
    public String[] findOne(String phoneNumber) {
        if (phoneNumber == null || phoneNumber.length() > 11 || phoneNumber.length() < 7) {
            return NOT_FOUND_RESULT;
        }
        int phoneNumberPrefix;
        try {
            phoneNumberPrefix = Integer.parseInt(phoneNumber.substring(0, 7));
        } catch (Exception e) {
            return NOT_FOUND_RESULT;
        }
        int left = 0;
        int right = phoneRecordCount;
        while (left <= right) {
            int middle = (left + right) >> 1;
            int currentOffset = indexAreaOffset + middle * INDEX_SEGMENT_LENGTH;
            if (currentOffset >= dataByteArray.length) {
                return NOT_FOUND_RESULT;
            }
            byteBuffer.position(currentOffset);
            int currentPrefix = byteBuffer.getInt();
            if (currentPrefix > phoneNumberPrefix) {
                right = middle - 1;
                continue;
            }
            if (currentPrefix < phoneNumberPrefix) {
                left = middle + 1;
                continue;
            }
            return getResult();

        }
        return NOT_FOUND_RESULT;
    }

    private String[] getResult() {
        int infoBeginOffset = byteBuffer.getInt();
        int phoneType = byteBuffer.get();
        int infoLength = -1;
        for (int i = infoBeginOffset; i < indexAreaOffset; ++i) {
            if (dataByteArray[i] == 0) {
                infoLength = i - infoBeginOffset;
                break;
            }
        }
        String infoString = new String(dataByteArray, infoBeginOffset, infoLength, StandardCharsets.UTF_8);
        // 0省 1市 2城市编码 3区号
        String[] infoSegments = infoString.split(INFO_STR_SPLIT);
        List<String> result = new ArrayList<>(Arrays.asList(infoSegments));
        // 运营商
        result.add(PHONE_NUMBER_TYPE[phoneType]);
        return result.toArray(new String[0]);
    }

    public String findOneToRow(String phoneNumber) {
        String[] infoSegments = findOne(phoneNumber);
        if (infoSegments.length != 5) {
            return NOT_FOUND_STR;
        }
        return infoSegments[0] + EXCEL_COL_SPLIT + infoSegments[1] + EXCEL_COL_SPLIT + infoSegments[2] + EXCEL_COL_SPLIT + infoSegments[3] + EXCEL_COL_SPLIT + infoSegments[4];
    }

    @Data
    @ToString
    public static class PhoneGeo {
        /**
         * 省
         */
        private String prov;
        /**
         * 市
         */
        private String city;
        /**
         * 区号
         */
        private String cityCode;
        /**
         * 城市编码
         */
        private String areaCode;
        /**
         * 运营商
         */
        private String telecom;

        private String phone;

        public PhoneGeo(String[] infoSegments, String phone) {
            this.phone = phone;
            if (infoSegments.length == 5) {
                this.prov = infoSegments[0];
                this.city = infoSegments[1];
                this.areaCode = infoSegments[2];
                this.cityCode = infoSegments[3];
                this.telecom = infoSegments[4];
                return;
            }
            this.prov = StringUtil.EMPTY;
            this.city = StringUtil.EMPTY;
            this.areaCode = StringUtil.EMPTY;
            this.cityCode = StringUtil.EMPTY;
            this.telecom = StringUtil.EMPTY;
        }
    }
}
